//
//  GRFMRemotePlayer.m
//  GRFMRemotePlayer_Example
//
//  Created by Guo Rui on 2018/1/15.
//  Copyright © 2018年 guorui. All rights reserved.
//

#import "GRFMRemotePlayer.h"
#import <AVFoundation/AVFoundation.h>
#import "NSURL+Steam.h"
#import "GRFMResourceLoaderDelegate.h"
@interface GRFMRemotePlayer()
{
    BOOL _isUserPause;
}
@property (strong, nonatomic) AVPlayer *player;
@property (strong, nonatomic) NSURL *url;

@property (strong, nonatomic) GRFMResourceLoaderDelegate *loaderDelegate;
@end

@implementation GRFMRemotePlayer

/**
 使用AVPlayer播放的三个步骤:
 1. 创建资源请求类AVAsset对象.这个类负责对url发起网络请求，获取媒体源数据.
 2. 将asset对象交给媒体资源管理器（AVPlayerItem）。这个对象会从源数据中获取媒体数据，例如时长，播放的当前时长，以及对媒体数据的操作。
 3. 播放器对象，用于媒体资源管理器的内容。
 
 note1: 播放器对象，应该在AVPlayerItem对象readyToPlay状态后，才可以播放
 note2: NSTimeInterval，实际上它是简单的双精度double类型，只是typedef了一下，但是由于浮点型数据计算很容易导致精度的丢失，在一些要求高精度的应用场景显然不适合，于是苹果在Core Media框架中定义了CMTime数据类型作为时间的格式。
 */

/**
 AVPlayer 支持本地音频播放和流媒体播放。
 1. 对于一个http协议的资源地址，avplayer使用的是下载后再播放的策略。
 2. 对于sreaming流媒体协议，avplayer使用的边下载边播放的策略。
 */

- (void)playWithURL:(NSURL *)url isCache:(BOOL)isCache {
    
    NSLog(@"path :%@", NSHomeDirectory());
    
    // 继续播放
    NSURL *assetUrl = [(AVURLAsset *)self.player.currentItem.asset URL];
    if ([url isEqual:assetUrl]) {
        [self resume];
        return;
    }
    
    if (isCache) {
        url = [url steamingURL];
    }
    
    // 新的播放
    self.url = url;
    
    
    // 1. 创建请求
    AVURLAsset *asset = [AVURLAsset assetWithURL:url];
    [asset.resourceLoader setDelegate:self.loaderDelegate queue:dispatch_get_main_queue()];
    
    if (self.player.currentItem) {
        // Note: 防止再次点击播放按钮时，观察者没有移除造成的崩溃。
        [self removeObserver];
    }
    
    // 2. 媒体资源管理器
    AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
    
    // 3. 交给播放器，播放
    self.player = [AVPlayer playerWithPlayerItem:item];
    
    [item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    // Note: playbackLikelyToKeepUp 表示资源是否可以流程播放。例如：拖动播放按钮到某个时间点，当加载这个时间点资源足够播放的时候，这个标志位会变为yes.
    [item addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil];
    
    // 注册监听。
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playToEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playToInterupt:) name:AVPlayerItemPlaybackStalledNotification object:nil];
}

#pragma mark - Getter
- (GRFMResourceLoaderDelegate *)loaderDelegate {
    
    if (!_loaderDelegate) {
        
        _loaderDelegate = [[GRFMResourceLoaderDelegate alloc] init];
    }
    return _loaderDelegate;
}

#pragma mark - Actions
- (void)playToEnd
{
    NSLog(@"播放结束");
    self.state = GRFMRemotePlayerStateStoped;
}

- (void)playToInterupt:(NSNotification *)not
{
    NSLog(@"%s - %@ - %@", __func__, not.object, not.userInfo);
    
    // 来电话等事件
    NSLog(@"播放中断");
    if (self.state == GRFMRemotePlayerStatePlaying) {
//        [self pause];
        self.state = GRFMRemotePlayerStatePause;
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status = [change[NSKeyValueChangeNewKey] intValue];
        if (status == AVPlayerStatusReadyToPlay) {
            [self resume];
            NSLog(@"资源准备好，可以播放了。");
        }
        else if (status == AVPlayerStatusFailed) {
            NSLog(@"播放遭遇到一个错误 - %@", self.player.error);
            self.state = GRFMRemotePlayerStateFailed;
        }
        else {
            NSLog(@"未知状态");
            self.state = GRFMRemotePlayerStateFailed;
        }
    }
    else if ([keyPath isEqualToString:@"playbackLikelyToKeepUp"]) {
        BOOL ptk = [change[NSKeyValueChangeNewKey] boolValue];
        if (ptk) {
            NSLog(@"加载了足够的资源，可以继续播放了");
            if (!_isUserPause) {
                [self resume];
            }
        }
        else {
            NSLog(@"加载了资源不足");
            self.state = GRFMRemotePlayerStateLoading;
        }
    }
}

- (void)removeObserver
{
    [self.player.currentItem removeObserver:self forKeyPath:@"status"];
    [self.player.currentItem removeObserver:self forKeyPath:@"playbackLikelyToKeepUp"];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)setState:(GRFMRemotePlayerState)state
{
    if (_state == state) {
        return;
    }
    
    _state = state;
    
    if (self.stateChange) {
        self.stateChange(state);
    }
}

#pragma mark - Public


- (void)pause
{
    _isUserPause = YES;
    [self.player pause];
    
    if (self.player) {
        self.state = GRFMRemotePlayerStatePause;
    }
}

- (void)resume
{
    _isUserPause = NO;
    [self.player play];
    
    if (self.player && self.player.currentItem.playbackLikelyToKeepUp) {
        self.state = GRFMRemotePlayerStatePlaying;
    }
}

- (void)stop {
    [self.player pause];
    self.player = nil;
    
    self.state = GRFMRemotePlayerStateStoped;
}

- (void)seekToProgress:(float)progress {
    
    if (progress < 0 || progress > 1) {
        return;
    }
    
    // CMTime 和NSTimeInterval是不同类型。 是比NSTimeInterval 更为精准的表示时间的数据结构。
    // CMTime -> NSTimeInterval, 用CMTimeGetSeconds()
    // NSTimeInterval -> CMTime, 用CMTimeMake（）
    NSTimeInterval totalTimeSec = [self totalTime];
    NSTimeInterval progressSec = totalTimeSec * progress;
    CMTime currentTime = CMTimeMake(progressSec, 1);
    
    [self.player.currentItem seekToTime:currentTime completionHandler:^(BOOL finished) {
        // 作用：加载这个时间点的资源是否成功。
        // 调用时机: 在拖动播放进度按钮的过程中，这里会一直被调用。如果currentTime这个时间点的资源没有加载完成，finish返回NO, 反之加载完了，返回YES.
        if (finished) {
            NSLog(@"这个时间点是有效的");
        }
        else {
            NSLog(@"这个时间点是无效的");
        }
    }];
}

- (void)seekToTimeDiffer:(NSTimeInterval)timeDiffet {
    NSTimeInterval currentSec = [self currentTime];
    currentSec += timeDiffet;
    NSTimeInterval totalTimeSec = [self totalTime];
    
    [self seekToProgress:currentSec / totalTimeSec];
}

- (void)setRate:(float)rate
{
    [self.player setRate:rate];
}

- (float)rate
{
    return self.player.rate;
}

- (void)setMuted:(BOOL)muted
{
    self.player.muted = muted;
}

- (BOOL)muted
{
    return self.player.muted;
}


- (void)setVolume:(float)volume {
    if (volume < 0 || volume > 1) {
        return;
    }
    
    [self setMuted:NO];
    [self.player setVolume:volume];
}

- (float)volume
{
    return self.player.volume;
}

- (NSTimeInterval)currentTime
{
    CMTime currentTime = self.player.currentItem.currentTime;
    NSTimeInterval currentTimeSec = CMTimeGetSeconds(currentTime);
    if (isnan(currentTimeSec)) {
        return 0;
    }
    return currentTimeSec;
}

- (NSTimeInterval)totalTime
{
    CMTime totalTime = self.player.currentItem.duration;
    NSTimeInterval totalTimeSec = CMTimeGetSeconds(totalTime);
    if (isnan(totalTimeSec)) {
        return 0;
    }
    return totalTimeSec;
}

- (NSString *)currentTimeFormat
{
    NSTimeInterval currentTime = [self currentTime];
    return [NSString stringWithFormat:@"%02zd : %02zd", (int)currentTime / 60, (int)currentTime % 60];
}

- (NSString *)totalTimeFormat
{
    NSTimeInterval totalTime = [self totalTime];
    return [NSString stringWithFormat:@"%02zd : %02zd", (int)totalTime / 60, (int)totalTime % 60];
}

- (float)progress
{
    if (self.totalTime == 0) {
        return 0;
    }
    
    return self.currentTime / self.totalTime;
}

- (float)loadDataProgress
{
    if (self.totalTime == 0) {
        return 0;
    }
    
    CMTimeRange timeRange = [self.player.currentItem.loadedTimeRanges.lastObject CMTimeRangeValue];
    CMTime loadTime = CMTimeAdd(timeRange.start, timeRange.duration);
    NSTimeInterval loadTimeSec = CMTimeGetSeconds(loadTime);
    return loadTimeSec / self.totalTime;
}


@end
